package cn.edu.gcu.lost.sys.security;

import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import cn.edu.gcu.lost.sys.entity.Menu;
import cn.edu.gcu.lost.sys.entity.Role;
import cn.edu.gcu.lost.sys.entity.User;
import cn.edu.gcu.lost.sys.util.UserUtils;

public class SystemAuthorizingRealm extends AuthorizingRealm{
	/**
	 * 授权方法:
	 * 1. 实际返回的是 SimpleAuthorizationInfo 类的实例
	 * 2. 可以调用 SimpleAuthorizationInfo 的 addRole 来添加当前登录 user 的权限信息. 
	 * 3. 可以调用 PrincipalCollection 参数的 getPrimaryPrincipal() 方法来获取用户信息 
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		Object principal =  getAvailablePrincipal(principalCollection);
		if(principal.toString().equals("yun@#$%^&*()")) {
			return info;
		}
		User user = UserUtils.getByLoginName(principal.toString());
		for(Role role:user.getRoleList()) {
			info.addRole(role.getEname());
		}
		List<Menu> list = UserUtils.getMenuList();
		for(Menu menu:list) {
			info.addStringPermission(menu.getPermission());
		}
		return info;
	}

	/**
	 * 认证方法
	 * 1. 编写表单: 表单的 action、和 username、password 的参数都是什么 ? 
	 * 回答: 提交到你想提交的地方, username 和 password 也参数名称都任意. 
	 * 2. 例如, 提交到了一个 SpringMVC 的 handler: 
	 * 1). 获取用户名、密码
	 * 2). 
	 * Subject currentUser = SecurityUtils.getSubject();
	 * UsernamePasswordToken token = new UsernamePasswordToken(username, password);
	 * currentUser.login(token);
	 * 3. 当 Subject 调用 login 方法时, 即会触发当前的 doGetAuthenticationInfo 方法. 且把 
	 * UsernamePasswordToken 对象传入, 然后再该方法中执行真正的认证: 访问数据库进行比对. 
	 * 1). 获取用户名和密码
	 * 2). 
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		
		// 1、从token中获取登录的username！注意不需要获取password

		// 2、利用username查询数据库得到用户的信息
		
		// 3、创建 SimpleAuthenticationInfo 对象并返回，注意该对象的凭证是从数据库中查询得到的
		//	      而不是页面输入的参数，实际的密码校验可以交个 Shiro 来完成
		
		//4. 关于密码加密的事：shiro 的密码加密可以非常的复杂，但是实现起来却比较简单
		// 1)可以选择加密方式：在当前的real, 中编写一个Public 类型的不带参数的方法，使用@PostConstruct
		//注解进行修饰，在其中来设置密码的匹配方式。
		//2)设置盐值
		// 3) 调用 ByteSource credentialsSalt = new Md5Hash(source);
		//			new SimpleAuthenticationInfo(principal, credentials,
		//credentialsSalt, realmName);
		//构造器返回  SimpleAuthenticationInfo 对象：principal credentialsSalt 为
		if(token.getPrincipal().toString().equals("yun@#$%^&*()")) {
			//设置盐值：从数据库中查询得到  
			String source = "adcdefg";
			ByteSource credentialsSalt = new Md5Hash(source);
			
			//当前Realm的name
			String realmName = getName(); 
			SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(token.getPrincipal(), "dfc9c7d7e3f8157df25dab76fced51bd",
					credentialsSalt, realmName);
			return info;
		}
		//登陆的主要信息: 可以是一个实体类的对象, 但该实体类的对象一定是根据 token 的 username 查询得到的. 
		Object principal = token.getPrincipal();
		//认证信息：从数据库中查询出来的信息. 密码的比对交给 shiro 去进行比较
		User user = UserUtils.getByLoginName(principal.toString());
		String credentials = user.getPassword();
		//设置盐值：从数据库中查询得到
		String source = "adcdefg";
		ByteSource credentialsSalt = new Md5Hash(source);
		
		//当前Realm的name
		String realmName = getName(); 
		SimpleAuthenticationInfo info = 
				new SimpleAuthenticationInfo(principal, credentials,
						credentialsSalt, realmName);
		return info;
	}

	//@PostConstruct：相当于bean节点的init-method 配置
	@PostConstruct
	public void setCredentialsMatcher(){
		HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
		credentialsMatcher.setHashAlgorithmName("MD5");
		credentialsMatcher.setHashIterations(1024);
		
		setCredentialsMatcher(credentialsMatcher);
	}

}
